<?php
/**
 * Created by PhpStorm.
 * User: yangos
 * Date: 18-6-16
 * Time: 下午3:36
 */

namespace vola;

class Loadder
{
    const MD5_TRIM = 1;
    const MD5_NOTRIM = 2;
    const HAVAL = 3;
    private static $prefixAll = [];
    private static $prefixDir = [];
    private static $all = [];
    private static $classMap = [], $missClass = [];
    private static $loadfile = [];
    private static $indent = [];
    private static $loadcontentfile = [];
    private static $instrace, $class_alias = [];

    /**
     * 创建实例化
     * @return static
     */
    public static function create()
    {
        if (empty(self::$instrace)) {
            self::add('vola\\', __DIR__);
            self::$instrace = new static();
        }
        return self::$instrace;
    }

    public static function autoComposer()
    {

        $froot = realpath(dirname(__FILE__) . '/../');
        $ds = DIRECTORY_SEPARATOR;

        if (file_exists(dirname($froot) . $ds . 'vendor/composer/autoload_namespaces.php')) {
            $map = include dirname($froot) . $ds . 'vendor/composer/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                \vola\Loadder::set($namespace, $path);
            }
        }

        if (file_exists(dirname($froot) . $ds . 'vendor/composer/autoload_psr4.php')) {
            $map = include dirname($froot) . $ds . 'vendor/composer/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                \vola\Loadder::add($namespace, $path);
            }
        }

        if (file_exists(dirname($froot) . $ds . 'vendor/composer/autoload_classmap.php')) {
            $map = include dirname($froot) . $ds . 'vendor/composer/autoload_classmap.php';
            \vola\Loadder::addClassmap($map);
        }

        if (file_exists(dirname($froot) . $ds . 'vendor/composer/autoload_files.php')) {
            $map = include dirname($froot) . $ds . 'vendor/composer/autoload_files.php';
            foreach ($map as $namespace => $path) {
                \vola\Loadder::requireFile($path);
            }
        }
    }

    /**
     * 修改已经保存的自动加载设置
     * @param $pre
     * @param string $path
     */
    public static function set($pre, $path = '')
    {
        if (!$pre) {
            self::$all = [$path];
        } else {
            $length = strlen($pre);
            if ($pre[$length - 1] !== '\\') {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            self::$prefixAll[$pre[0]][$pre] = $length;
            self::$prefixDir[$pre] = (array)$path;
        }
    }

    /**
     * 注册alise
     * @param $pre
     * @param string $path
     */
    public static function alise($flag, $class = '')
    {
        if (is_array($flag)) {
            foreach ($flag as $name => $classname) {
                self::$class_alias[$name] = $classname;
            }
        } else {
            self::$class_alias[$flag] = $class;
        }
    }

    /**
     * 添加自动加载
     * @param $pre
     * @param string $path
     */
    public static function add($pre, $path = '')
    {
        if (!$pre) {
            if (!is_array($path)) $path = [$path];
            self::$all = array_merge(self::$all, $path);
        } else {
            if (!isset(self::$prefixDir[$pre])) {
                $length = strlen($pre);
                if ($pre[$length - 1] !== '\\') {
                    throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
                }
                self::$prefixAll[$pre[0]][$pre] = $length;
                self::$prefixDir[$pre] = (array)$path;
            } else {
                self::$prefixDir[$pre] = array_merge(self::$prefixDir[$pre], (array)$path);
            }
        }
    }

    /**
     * 添加自动加载
     * @param $pre
     * @param string $path
     */
    public static function addClassmap(array $class)
    {
        if (empty(self::$classMap)) {
            self::$classMap = array_merge(self::$classMap, $class);
        } else {
            self::$classMap = $class;
        }
    }

    /**
     * 监听自动加载
     */
    public static function listen()
    {
        spl_autoload_register([self::create(), 'load'], true, true);
    }

    /**
     * 加载class 自动加载方法
     * @param $class
     * @return bool
     */
    public function load($class)
    {
        // 不存在的类加载
        // 判断如果已经加载过 返回去
        if (class_exists($class, false)) {
            return true;
        }

        // 转换类名为大写, 规范
        if (preg_match('/\\\([a-z0-9]+)$/', $class)) {
            $class = preg_replace_callback('/\\\([a-z0-9]+)$/', function ($match) {
                return '\\' . ucfirst($match[1]);
            }, $class);
        }

        // 判断是不是别名， 是别名就加载别名 别名意思就是 abc 这个类相当于 \a\b\c\abc 给他一个简写
        if (isset(self::$class_alias[$class])) {
            return class_alias(self::$class_alias[$class], $class);
        }

        // 都不存在 开始查找
        if ($file = $this->find($class)) {
            self::includeFile($file);
            return true;
        }
    }

    /**
     * 基础处理
     * @param $class
     * @return bool|mixed|string
     */
    private function find($class)
    {
        // 已经加载过，返回去
        if (isset(self::$classMap[$class])) {
            return self::$classMap[$class];
        }

        // 不存在的 返回去
        if (isset(self::$missClass[$class])) {
            return false;
        }

        // 然后查找文件
        $file = $this->findFile($class);

        if ($file === false) {
            self::$missClass[$class] = true;
            return false;
        }
        self::$classMap[$class] = $file;
        return $file;
    }

    /**
     * 查找文件
     * @param $class
     * @param string $ext
     * @return string
     */
    private function findFile($class, $ext = '.php')
    {
        // 初始化文件
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
        // 检查目录
        $first = $class[0];
        // $first 第一个字母，最快的第一步排查方法
        if (isset(self::$prefixAll[$first])) {
            $subPath = $class;
            /**
             * while这一段 相当于 倒着开始算
             * 倒着取\\后的字符串 这个相当于是类的名字
             * 前面部分就是命名空间，循环一直到 \\ 都没得取了 当然就没有了
             */
            while (false !== ($last = strrpos($subPath, '\\'))) {
                $subPath = substr($subPath, 0, $last);
                $search = $subPath . '\\';
                if (isset(self::$prefixDir[$search])) {
                    foreach (self::$prefixDir[$search] as $dir) {
                        $length = self::$prefixAll[$first][$search];
                        // 检查文件是否存在
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                            return $file;
                        }
                    }
                }
            }
        }
    }

    /**
     * 引入文件, 全局
     * @param $file
     */
    public static function includeFile($file, $assign = [])
    {
        if (empty(self::$loadfile[self::crypt($file, ['trim' => true])])) {
            if (!empty($assign)) {
                extract($assign, EXTR_SKIP);
            }
            include $file;
            self::$loadfile[self::crypt($file, ['trim' => true])] = true;
        }
    }

    public static function requireFile($file)
    {
        if (empty(self::$indent[self::crypt($file, ['trim' => true])])) {
            require $file;
            self::$indent[self::crypt($file, ['trim' => true])] = true;
        }
    }

    /**
     * 引入文件并返回文件内容, 全局
     * @param $file
     * @return mixed
     */
    public static function getContentOfFile($file)
    {
        if (empty(self::$loadcontentfile[self::crypt($file, ['trim' => true])])) {
            $data = include $file;
        } else {
            $data = self::$loadcontentfile[self::crypt($file, ['trim' => true])];
        }
        return $data;
    }


    public static function crypt($str, $type = 2) {
        switch ($type) {
            case 1:
                $salt = '$1$';
                $trim = true;
                $str = crypt($str, $salt);
                break;
            case 3:
                $trim = false;
                $str = hash('haval160,4', $str);
                break;
            case 2:
            default:
                $salt = '$1$';
                $trim = false;
                $str = crypt($str, $salt);
        }

        if ($trim) {
            return str_replace(['/', '.'], '', $str);
        }
        return $str;
    }
}
